<?php
/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */


/**
 *http请求处理
 */
class HttpUtil
{
    public static function send($request, $readtimeout, $connectTimeout)
    {
        return self::DoHttp($request->getHost(),
            $request->getPath(),
            $request->getMethod(),
            $request->getAppKey(),
            $request->getAppSecret(),
            $readtimeout,
            $connectTimeout,
            $request->getHeaders(),
            $request->getQuerys(),
            $request->getBodys(),
            $request->getSignHeaders());
    }

    /**
     *请求Request
     */
    private static function DoHttp($host, $path, $method, $appKey, $appSecret, $readtimeout, $connectTimeout, $headers, $querys, $bodys, $signHeaderPrefixList)
    {
        $response = new HttpResponse();
        $headers = self::InitialBasicHeader($path, $appKey, $appSecret, $method, $headers, $querys, $bodys, $signHeaderPrefixList);
        $curl = self::InitHttpRequest($host, $path, $method, $readtimeout, $connectTimeout, $headers, $querys);

        $streams = array();
        if (is_array($bodys)) {
            if (0 < count($bodys)) {
                $body = "";
                foreach ($bodys as $itemKey => $itemValue) {
                    if (0 < strlen($body)) {
                        $body .= "&";
                    }
                    if (0 < strlen($itemValue) && 0 == strlen($itemKey))
                    {
                        $body .= $itemValue;
                        array_push($streams, $itemValue);
                    }
                    if (0 < strlen($itemKey)) {
                        $body .= "=";
                        if (0 < strlen($itemValue)) {
                            $body .= URLEncode($itemValue);
                        }
                    }
                }
                if (count($bodys) == count($streams) && 1 == count($streams)) {
                    curl_setopt($curl, CURLOPT_POSTFIELDS, $streams[0]);
                } elseif (0 < count($bodys)) {
                    curl_setopt($curl, CURLOPT_POSTFIELDS, http_build_query($bodys));
                }
            }
        }
        $response->setContent(curl_exec($curl));
        $response->setHttpStatusCode(curl_getinfo($curl, CURLINFO_HTTP_CODE));
        $response->setContentType(curl_getinfo($curl, CURLINFO_CONTENT_TYPE));
        $response->setHeaderSize(curl_getinfo($curl, CURLINFO_HEADER_SIZE));
        curl_close($curl);
        return $response;
    }

    /**
     *准备请求Request
     */
    private static function InitHttpRequest($host, $path, $method, $readtimeout, $connectTimeout, $headers, $querys)
    {
        $url = $host;
        if (0 < strlen($path)) {
            $url.= $path;
        }
        $headerArray = array();
        if (is_array($headers)) {
            if (0 < count($headers)) {
                foreach ($headers as $itemKey => $itemValue) {
                    if (0 < strlen($itemKey)) {
                        array_push($headerArray, $itemKey.":".$itemValue);
                    }
                }
            }
        }
        if (is_array($querys)) {
            if (0 < count($querys)) {
                $sb = "";
                foreach ($querys as $itemKey => $itemValue) {
                    if (0 < strlen($sb)) {
                        $sb .= "&";
                    }
                    if (0 < strlen($itemValue) && 0 == strlen($itemKey))
                    {
                        $sb .= $itemValue;
                    }
                    if (0 < strlen($itemKey)) {
                        $sb .= $itemKey;
                        if (0 < strlen($itemValue)) {
                            $sb .= "=";
                            $sb .= URLEncode($itemValue);
                        }
                    }
                }
                $url .= "?";
                $url .= $sb;
            }
        }

        $curl = curl_init();
        curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $method);
        curl_setopt($curl, CURLOPT_URL, $url);
        curl_setopt($curl, CURLOPT_HTTPHEADER, $headerArray);
        curl_setopt($curl, CURLOPT_FAILONERROR, false);
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($curl, CURLOPT_TIMEOUT, $readtimeout);
        curl_setopt($curl, CURLOPT_CONNECTTIMEOUT, $connectTimeout);
        curl_setopt($curl, CURLOPT_HEADER, true);

        if (1 == strpos("$".$host, 'https://'))
        {
            curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
            curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, false);
        }
        curl_setopt($curl, CURLOPT_HEADER, true);
        return $curl;
    }

    /**
     *准备请求的基本header
     */
    private static function InitialBasicHeader($path, $appKey, $appSecret, $method, $headers, $querys, $bodys, $signHeaderPrefixList)
    {
        if (null == $headers) {
            $headers = array();
        }
        $sb = "";
        //时间戳
        date_default_timezone_set('PRC');
        $headers[SystemHeader::X_CA_TIMESTAMP] = strval(time()*1000);
        //防重放，协议层不能进行重试，否则会报NONCE被使用；如果需要协议层重试，请注释此行
        $headers[SystemHeader::X_CA_NONCE] = strval(self::NewGuid());

        $headers[SystemHeader::X_CA_KEY] = $appKey;
        $headers[SystemHeader::X_CA_SIGNATURE] = SignUtil::Sign($path, $method, $appSecret, $headers, $querys, $bodys, $signHeaderPrefixList);

        return $headers;
    }

    public static function CheckValidationResult($sender, $certificate, $chain, $errors)
    {
        return true;
    }

    private static function NewGuid()
    {
        mt_srand((double)microtime()*10000);
        $uuid = strtoupper(md5(uniqid(rand(), true)));
        return $uuid;
    }
}


/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
class HttpResponse
{
    private $content;
    private $body;
    private $header;
    private $requestId;
    private $errorMessage;
    private $contentType;
    private $httpStatusCode;

    public function getContent()
    {
        return $this->content;
    }

    public function setContent($content)
    {
        $this->content = $content;
    }

    public function setHeader($header)
    {
        $this->header = $header;
    }

    public function getHeader()
    {
        return $this->header;
    }

    public function setBody($body)
    {
        $this->body = $body;
    }

    public function getBody()
    {
        return $this->body;
    }

    public function getRequestId()
    {
        return $this->requestId;
    }

    public function getErrorMessage()
    {
        return $this->errorMessage;
    }

    public function getHttpStatusCode()
    {
        return $this->httpStatusCode;
    }

    public function setHttpStatusCode($httpStatusCode)
    {
        $this->httpStatusCode  = $httpStatusCode;
    }

    public function getContentType()
    {
        return $this->contentType;
    }

    public function setContentType($contentType)
    {
        $this->contentType  = $contentType;
    }

    public function getSuccess()
    {
        if(200 <= $this->httpStatusCode && 300 > $this->httpStatusCode)
        {
            return true;
        }
        return false;
    }

    /**
     *根据headersize大小，区分返回的header和body
     */
    public function setHeaderSize($headerSize) {
        if (0 < $headerSize && 0 < strlen($this->content)) {
            $this->header = substr($this->content, 0, $headerSize);
            self::extractKey();
        }
        if (0 < $headerSize && $headerSize < strlen($this->content)) {
            $this->body = substr($this->content, $headerSize);
        }
    }

    /**
     *提取header中的requestId和errorMessage
     */
    private function extractKey() {
        if (0 < strlen($this->header)) {
            $headers = explode("\r\n", $this->header);
            foreach ($headers as $value) {
                if(strpos($value, "X-Ca-Request-Id:") !== false)
                {
                    $this->requestId = trim(substr($value, strlen("X-Ca-Request-Id:")));
                }
                if(strpos($value, "X-Ca-Error-Message:") !== false)
                {
                    $this->errorMessage = trim(substr($value, strlen("X-Ca-Error-Message:")));
                }
            }
        }
    }
}


/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

/**
 *签名处理
 */
class SignUtil
{
    /**
     * 构建待签名
     */
    public static function Sign($path, $method, $secret, &$headers, $querys, $bodys, $signHeaderPrefixList)
    {
        $signStr = self::BuildStringToSign($path, $method, $headers, $querys, $bodys, $signHeaderPrefixList);
        return base64_encode(hash_hmac('sha256', $signStr, $secret, true));
    }
    /**
     * 构建待签名path+(header+query+body)
     */
    private static function BuildStringToSign($path, $method, &$headers, $querys, $bodys, $signHeaderPrefixList)
    {
        $sb = "";
        $sb.= strtoupper($method);
        $sb.= Constants::LF;
        if (array_key_exists(HttpHeader::HTTP_HEADER_ACCEPT, $headers) && null != $headers[HttpHeader::HTTP_HEADER_ACCEPT]) {
            $sb.= $headers[HttpHeader::HTTP_HEADER_ACCEPT];
        }
        $sb.= Constants::LF;
        if (array_key_exists(HttpHeader::HTTP_HEADER_CONTENT_MD5, $headers) && null != $headers[HttpHeader::HTTP_HEADER_ACCEPT]) {
            $sb.= $headers[HttpHeader::HTTP_HEADER_CONTENT_MD5];
        }
        $sb.= Constants::LF;
        if (array_key_exists(HttpHeader::HTTP_HEADER_CONTENT_TYPE, $headers) && null != $headers[HttpHeader::HTTP_HEADER_ACCEPT]) {
            $sb.= $headers[HttpHeader::HTTP_HEADER_CONTENT_TYPE];
        }
        $sb.= Constants::LF;
        if (array_key_exists(HttpHeader::HTTP_HEADER_DATE, $headers) && null != $headers[HttpHeader::HTTP_HEADER_ACCEPT]) {
            $sb.= $headers[HttpHeader::HTTP_HEADER_DATE];
        }
        $sb.= Constants::LF;
        $sb.= self::BuildHeaders($headers, $signHeaderPrefixList);
        $sb.= self::BuildResource($path, $querys, $bodys);

        return $sb;
    }

    /**
     * 构建待签名Path+Query+FormParams
     */
    private static function BuildResource($path, $querys, $bodys)
    {
        $sb = "";
        if (0 < strlen($path))
        {
            $sb.=$path;
        }
        $sbParam = "";
        $sortParams = array();

        //query参与签名
        if (is_array($querys)) {
            foreach ($querys as $itemKey => $itemValue) {
                if (0 < strlen($itemKey)) {
                    $sortParams[$itemKey] = $itemValue;
                }
            }
        }
        //body参与签名
        if (is_array($bodys)) {
            foreach ($bodys as $itemKey => $itemValue) {
                if (0 < strlen($itemKey)) {
                    $sortParams[$itemKey] = $itemValue;
                }
            }
        }
        //排序
        ksort($sortParams);
        //参数Key
        foreach ($sortParams as $itemKey => $itemValue) {
            if (0 < strlen($itemKey)) {
                if (0 < strlen($sbParam)) {
                    $sbParam.="&";
                }
                $sbParam.=$itemKey;
                if (null != $itemValue)
                {
                    if (0 < strlen($itemValue)) {
                        $sbParam.="=";
                        $sbParam.=$itemValue;
                    }
                }
            }
        }
        if (0 < strlen($sbParam)) {
            $sb.="?";
            $sb.=$sbParam;
        }

        return $sb;
    }

    /**
     * 构建待签名Http头
     *
     * @param headers              请求中所有的Http头
     * @param signHeaderPrefixList 自定义参与签名Header前缀
     * @return 待签名Http头
     */
    private static function BuildHeaders(&$headers, $signHeaderPrefixList)
    {
        $sb = "";

        if (null != $signHeaderPrefixList)
        {
            //剔除X-Ca-Signature/X-Ca-Signature-Headers/Accept/Content-MD5/Content-Type/Date
            unset($signHeaderPrefixList[SystemHeader::X_CA_SIGNATURE]);
            unset($signHeaderPrefixList[HttpHeader::HTTP_HEADER_ACCEPT]);
            unset($signHeaderPrefixList[HttpHeader::HTTP_HEADER_CONTENT_MD5]);
            unset($signHeaderPrefixList[HttpHeader::HTTP_HEADER_CONTENT_TYPE]);
            unset($signHeaderPrefixList[HttpHeader::HTTP_HEADER_DATE]);
            ksort($signHeaderPrefixList);

            if (is_array($headers)) {
                ksort($headers);
                $signHeadersStringBuilder = "";
                foreach ($headers as $itemKey => $itemValue)
                {
                    if (self::IsHeaderToSign($itemKey, $signHeaderPrefixList))
                    {
                        $sb.=$itemKey;
                        $sb.=Constants::SPE2;
                        if (0 < strlen($itemValue)) {
                            $sb.=$itemValue;
                        }
                        $sb.=Constants::LF;
                        if (0 < strlen($signHeadersStringBuilder))
                        {
                            $signHeadersStringBuilder.= Constants::SPE1;
                        }
                        $signHeadersStringBuilder.= $itemKey;
                    }
                }
                $headers[SystemHeader::X_CA_SIGNATURE_HEADERS] = $signHeadersStringBuilder;
            }

        }

        return $sb;
    }
    /**
     * Http头是否参与签名
     * return
     */
    private static function IsHeaderToSign($headerName, $signHeaderPrefixList)
    {
        if (NULL == $headerName) {
            return false;
        }
        if (0 == strlen($headerName)) {
            return false;
        }
        if (1 == strpos("$".$headerName, Constants::CA_HEADER_TO_SIGN_PREFIX_SYSTEM)) {
            return true;
        }
        if (!is_array($signHeaderPrefixList) || empty($signHeaderPrefixList) ) {
            return false;
        }
        if (array_key_exists($headerName, $signHeaderPrefixList)) {
            return true;
        }

        return false;
    }
}

/**
 * 常用HTTP Content-Type常量
 */
class ContentType {
    //表单类型Content-Type
    const CONTENT_TYPE_FORM = "application/x-www-form-urlencoded; charset=UTF-8";
    // 流类型Content-Type
    const CONTENT_TYPE_STREAM = "application/octet-stream; charset=UTF-8";
    //JSON类型Content-Type
    const CONTENT_TYPE_JSON = "application/json; charset=UTF-8";
    //XML类型Content-Type
    const CONTENT_TYPE_XML = "application/xml; charset=UTF-8";
    //文本类型Content-Type
    const CONTENT_TYPE_TEXT = "application/text; charset=UTF-8";
}

/**
 * ͨ�ó���
 */
class Constants
{
    //ǩ���㷨HmacSha256
    const HMAC_SHA256 = "HmacSHA256";
    //����UTF-8
    const ENCODING = "UTF-8";
    //UserAgent
    const USER_AGENT = "demo/aliyun/java";
    //���з�
    const LF = "\n";
    //�ָ���1
    const SPE1 = ",";
    //�ָ���2
    const SPE2 = ":";
    //Ĭ������ʱʱ��,��λ����
    const DEFAULT_TIMEOUT = 1000;
    //����ǩ����ϵͳHeaderǰ׺,ֻ��ָ��ǰ׺��Header�Ż���뵽ǩ����
    const CA_HEADER_TO_SIGN_PREFIX_SYSTEM = "X-Ca-";
}

/**
 * HTTP头常量
 */
class HttpHeader {
    //请求Header Accept
    const HTTP_HEADER_ACCEPT = "Accept";
    //请求Body内容MD5 Header
    const HTTP_HEADER_CONTENT_MD5 = "Content-MD5";
    //请求Header Content-Type
    const HTTP_HEADER_CONTENT_TYPE = "Content-Type";
    //请求Header UserAgent
    const HTTP_HEADER_USER_AGENT = "User-Agent";
    //请求Header Date
    const HTTP_HEADER_DATE = "Date";
}

/**
 * 系统HTTP头常量
 */
class SystemHeader {
    //签名Header
    const X_CA_SIGNATURE = "X-Ca-Signature";
    //所有参与签名的Header
    const X_CA_SIGNATURE_HEADERS = "X-Ca-Signature-Headers";
    //请求时间戳
    const X_CA_TIMESTAMP = "X-Ca-Timestamp";
    //请求放重放Nonce,15分钟内保持唯一,建议使用UUID
    const X_CA_NONCE = "X-Ca-Nonce";
    //APP KEY
    const X_CA_KEY = "X-Ca-Key";
}
class HttpClient
{
    private static $connectTimeout = 30000;//30 second
    private static $readTimeout	= 80000;//80 second

    public static function execute($request)
    {
        return HttpUtil::send($request, self::$readTimeout, self::$connectTimeout);
    }
}



/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
class HttpRequest
{
    protected  $host;
    protected  $path;
    protected  $method;
    protected  $appKey;
    protected  $appSecret;
    protected  $headers = array();
    protected  $signHeaders = array();
    protected  $querys = array();
    protected  $bodys = array();

    function  __construct($host, $path, $method, $appKey, $appSecret)
    {
        $this->host = $host;
        $this->path = $path;
        $this->method = $method;
        $this->appKey = $appKey;
        $this->appSecret = $appSecret;
    }

    public function getHeaders()
    {
        return $this->headers;
    }

    public function setHeader($key, $value)
    {
        if (null == $this->headers) {
            $this->headers = array();
        }
        $this->headers[$key] = $value;
    }

    public function getHeader($key)
    {
        return $this->headers[$key];
    }

    public function removeHeader($key)
    {
        unset($this->headers[$key]);
    }

    public function getQuerys()
    {
        return $this->querys;
    }

    public function setQuery($key, $value)
    {
        if (null == $this->querys) {
            $this->querys = array();
        }
        $this->querys[$key] = $value;
    }

    public function getQuery($key)
    {
        return $this->querys[$key];
    }

    public function removeQuery($key)
    {
        unset($this->querys[$key]);
    }

    public function getBodys()
    {
        return $this->bodys;
    }

    public function setBody($key, $value)
    {
        if (null == $this->bodys) {
            $this->bodys = array();
        }
        $this->bodys[$key] = $value;
    }

    public function getBody($key)
    {
        return $this->bodys[$key];
    }

    public function removeBody($key)
    {
        unset($this->bodys[$key]);
    }

    public function setBodyStream($value)
    {
        if (null == $this->bodys) {
            $this->bodys = array();
        }
        $this->bodys[""] = $value;
    }

    public function setBodyString($value)
    {
        if (null == $this->bodys) {
            $this->bodys = array();
        }
        $this->bodys[""] = $value;
    }


    public function getSignHeaders()
    {
        return $this->signHeaders;
    }

    public function setSignHeader($value)
    {
        if (null == $this->signHeaders) {
            $this->signHeaders = array();
        }
        if (!in_array($value, $this->signHeaders)) {
            array_push($this->signHeaders, $value);
        }
    }

    public function removeSignHeader($value)
    {
        unset($this->signHeaders[$value]);
    }

    public function getHost()
    {
        return $this->host;
    }

    public function setHost($host)
    {
        $this->host = $host;
    }

    public function getPath()
    {
        return $this->path;
    }

    public function setPath($path)
    {
        $this->path = $path;
    }

    public function getMethod()
    {
        return $this->method;
    }

    public function setMethod($method)
    {
        $this->method = $method;
    }

    public function getAppKey()
    {
        return $this->appKey;
    }

    public function setAppKey($appKey)
    {
        $this->appKey = $appKey;
    }

    public function getAppSecret()
    {
        return $this->appSecret;
    }

    public function setAppSecret($appSecret)
    {
        $this->appSecret = $appSecret;
    }
}